﻿//winboard receiving messages
namespace FSharpChess
open System
open System.Threading
open System.Windows.Forms

module WBi =
    let mutable threadWinBoardListener:Thread = null
    exception WinBoardInputException of string
    let wbMakeMove(strMove:string) =
(*        See below for the syntax of moves. If the move is illegal, print an error message; see the section "Commands from the engine to xboard". If the move is legal and in turn, make it. If not in force mode, stop the opponent's clock, start the engine's clock, start thinking, and eventually make a move. 
        When xboard sends your engine a move, it normally sends coordinate algebraic notation. Examples: 

        Normal moves: e2e4  
        Pawn promotion: e7e8q  
        Castling: e1g1, e1c1, e8g8, e8c8  
        Bughouse/crazyhouse drop: P@h3  
        ICS Wild 0/1 castling: d1f1, d1b1, d8f8, d8b8  
        FischerRandom castling: O-O, O-O-O (oh, not zero)  

        Beginning in protocol version 2, you can use the feature command to select SAN (standard algebraic notation) instead; for example, e4, Nf3, exd5, Bxf7+, Qxf7#, e8=Q, O-O, or P@h3. Note that the last form, P@h3, is a extension to the PGN standard's definition of SAN, which does not support bughouse or crazyhouse. 

        xboard doesn't reliably detect illegal moves, because it does not keep track of castling unavailability due to king or rook moves, or en passant availability. If xboard sends an illegal move, send back an error message so that xboard can retract it and inform the user; see the section "Commands from the engine to xboard". *)
        Funcs.SuspendPondering()
        let movename = 
            if (strMove.Length=5) then
                match strMove.Substring(4,1) with
                    |"q" -> PawnPromotionQueen
                    |"r" -> PawnPromotionRook
                    |"b" -> PawnPromotionBishop
                    |"n" -> PawnPromotionKnight
                    |_ -> NullMove
            else NullMove
        let moves = Move_gen.GenerateLegalMovesPr Funcs.gm
        let rec testmove mvl =
            match mvl with
                |[] ->  raise(WinBoardInputException("Illegal move: " + strMove))
                |_  ->  let move = mvl.Head
                        if (Ref.sq.[move.From]=strMove.Substring(0,2) && Ref.sq.[move.To]=strMove.Substring(2,2) && (movename=NullMove || move.mvName=movename ) ) then
                            Funcs.MakeAMove(move)
                        else testmove mvl.Tail
        testmove (ResizeArray.toList moves)
    let wbSetLevel(strLevel:string) =
(*        // MPS BASE INC
        In conventional clock mode, every time control period is the same. That is, if the time control is 
        40 moves in 5 minutes, then after each side has made 40 moves, they each get an additional 5 minutes, 
        and so on, ad infinitum. At future time it would be nice to support a series of distinct time 
        controls. This is very low on my personal priority list, but code donations to the xboard project 
        are accepted, so feel free to take a swing at it. I suggest you talk to me first, though. 

        The command to set a conventional time control looks like this: 

        level 40 5 0
        level 40 0:30 0

        The 40 means that there are 40 moves per time control. The 5 means there are 5 minutes in the control. In the second example, the 0:30 means there are 30 seconds. The final 0 means that we are in conventional clock mode. *)
        let intPos = strLevel.IndexOf(" ")
        let strMoves = strLevel.Substring(0, intPos)
        let strLevel = strLevel.Substring(intPos+1)
        let intPos = strLevel.IndexOf(" ")
        let strTime = strLevel.Substring(0, intPos)
        let strLevel = strLevel.Substring(intPos+1)
        let strIncrement = strLevel
        let intIncrement = System.Int32.Parse(strIncrement)
        let intMoves = System.Int32.Parse(strMoves)
        let intPos = strTime.IndexOf(':')
        let mutable intMinutes = System.Int32.Parse(strTime)
        let mutable intSeconds = 0
        if (intPos>=0) then
            intMinutes <- System.Int32.Parse(strTime.Substring(0, intPos))
            intSeconds <- System.Int32.Parse(strTime.Substring(intPos+1))
        Funcs.ClockMoves <- intMoves
        Funcs.gmtim.ClockTime <- new TimeSpan(0, intMinutes, intSeconds)
        Funcs.gmtim.ClockIncrementPerMove <- new TimeSpan(0, 0, intIncrement)
        Funcs.gmtim.ClockFixedTimePerMove <- new TimeSpan(0, 0, 0)
        Funcs.DifficultyLevel <- 0
        Search.MaximumSearchDepth <- 32
        Funcs.EnablePondering <- true
    let ProcessInputEvent(strMessage:string) =
        try
            if (strMessage.StartsWith("xboard")) then
                (*
                This command will be sent once immediately after your engine process is started. 
                You can use it to put your engine into "xboard mode" if that is needed. 
                If your engine prints a prompt to ask for user input, you must turn off the prompt and output a 
                newline when the "xboard" command comes in.
                *)
                // This will be a false 2nd "ghost" message, so ignore it.
                ()
            elif (strMessage.StartsWith("protover ")) then
                (*
                ping (boolean, default 0, recommended 1) 
                If ping=1, xboard may use the protocol's new "ping" command; if ping=0, xboard will not use the command. 
                setboard (boolean, default 0, recommended 1) 
                If setboard=1, xboard will use the protocol's new "setboard" command to set up positions; if setboard=0, it will use the older "edit" command. 
                playother (boolean, default 0, recommended 1) 
                If playother=1, xboard will use the protocol's new "playother" command when appropriate; if playother=0, it will not use the command. 
                san (boolean, default 0) 
                If san=1, xboard will send moves to the engine in standard algebraic notation (SAN); for example, Nf3. If san=0, xboard will send moves in coordinate notation; for example, g1f3. See MOVE in section 8 above for more details of both kinds of notation. 
                usermove (boolean, default 0) 
                If usermove=1, xboard will send moves to the engine with the command "usermove MOVE"; if usermove=0, xboard will send just the move, with no command name. 
                time (boolean, default 1, recommended 1) 
                If time=1, xboard will send the "time" and "otim" commands to update the engine's clocks; if time=0, it will not. 
                draw (boolean, default 1, recommended 1) 
                If draw=1, xboard will send the "draw" command if the engine's opponent offers a draw; if draw=0, xboard will not inform the engine about draw offers. Note that if draw=1, you may receive a draw offer while you are on move; if this will cause you to move immediately, you should set draw=0. 
                sigint (boolean, default 1) 
                If sigint=1, xboard may send SIGINT (the interrupt signal) to the engine as section 7 above; if sigint=0, it will not. 
                sigterm (boolean, default 1) 
                If sigterm=1, xboard may send SIGTERM (the termination signal) to the engine as section 7 above; if sigterm=0, it will not. 
                reuse (boolean, default 1, recommended 1) 
                If reuse=1, xboard may reuse your engine for multiple games. If reuse=0 (or if the user has set the -xreuse option on xboard's command line), xboard will kill the engine process after every game and start a fresh process for the next game. 
                analyze (boolean, default 1, recommended 1) 
                If analyze=0, xboard will not try to use the "analyze" command; it will pop up an error message if the user asks for analysis mode. If analyze=1, xboard will try to use the command if the user asks for analysis mode. 
                myname (string, default determined from engine filename) 
                This feature lets you set the name that xboard will use for your engine in window banners, in the PGN tags of saved game files, and when sending the "name" command to another engine. 
                variants (string, see text below) 
                This feature indicates which chess variants your engine accepts. It should be a comma-separated list of variant names. See the table under the "variant" command in section 8 above. If you do not set this feature, xboard will assume by default that your engine supports all variants. (However, the -zippyVariants command-line option still limits which variants will be accepted in Zippy mode.) It is recommended that you set this feature to the correct value for your engine (just "normal" in most cases) rather than leaving the default in place, so that the user will get an appropriate error message if he tries to play a variant that your engine does not support. 
                colors (boolean, default 1, recommended 0) 
                If colors=1, xboard uses the obsolete "white" and "black" commands in a stylized way that works with most older chess engines that require the commands. See the "Idioms" section below for details. If colors=0, xboard does not use the "white" and "black" commands at all. 
                ics (boolean, default 0) 
                If ics=1, xboard will use the protocol's new "ics" command to inform the engine of whether or not it is playing on a chess server; if ics=0, it will not. 
                name (boolean, see text below) 
                If name=1, xboard will use the protocol's "name" command to inform the engine of the opponent's name; if name=0, it will not. By default, name=1 if the engine is playing on a chess server; name=0 if not. 
                pause (boolean, default 0) 
                If pause=1, xboard may use the protocol's new "pause" command; if pause=0, xboard assumes that the engine does not support this command. 
                done (integer, no default) 
                If you set done=1 during the initial two-second timeout after xboard sends you the "xboard" command, the timeout will end and xboard will not look for any more feature commands before starting normal operation. If you set done=0, the initial timeout is increased to one hour; in this case, you must set done=1 before xboard will enter normal operation. 
                *)
                let mutable strFeatures = ""
                strFeatures <- strFeatures + " ping=1 "
                strFeatures <- strFeatures + " setboard=1"
                strFeatures <- strFeatures + " playother=1"
                strFeatures <- strFeatures + " san=0"
                strFeatures <- strFeatures + " usermove=1"
                strFeatures <- strFeatures + " time=1"
                strFeatures <- strFeatures + " draw=0"
                strFeatures <- strFeatures + " sigint=0"
                strFeatures <- strFeatures + " sigterm=0"
                strFeatures <- strFeatures + " reuse=1"
                strFeatures <- strFeatures + " analyze=1"
                strFeatures <- strFeatures + " myname=\"FSharpChess\""
                strFeatures <- strFeatures + " variants=\"normal\""
                strFeatures <- strFeatures + " colors=0"
                strFeatures <- strFeatures + " ics=0"
                strFeatures <- strFeatures + " name=0"
                strFeatures <- strFeatures + " pause=1"
                strFeatures <- strFeatures + " done=1"
                Funcs.wbSendOutputMessage("feature" + strFeatures)
            elif (strMessage.StartsWith("accepted ")) then
                // Feature request is accepted.
                ()
            elif (strMessage.StartsWith("rejected ")) then
                // Feature request is rejected.
                ()
            elif (strMessage="new") then
                Funcs.SuspendPondering()
                Funcs.DifficultyLevel <- 0
                Funcs.ClockMoves <- 40
                Funcs.gmtim.ClockTime <- new TimeSpan(0, 5, 0)
                Search.MaximumSearchDepth <- 32
                Funcs.EnablePondering <- false
                Funcs.gmtim.ClockIncrementPerMove <- new TimeSpan(0, 0, 0)
                Funcs.gmtim.ClockFixedTimePerMove <- new TimeSpan(0, 0, 0)
                Attack.PlayerWhite.Intellegence <- Human
                Attack.PlayerBlack.Intellegence <- Human
                Funcs.New("")
                Funcs.SuspendPondering()
                Attack.PlayerWhite.Intellegence <- Human
                Attack.PlayerBlack.Intellegence <- Computer
                Funcs.ResumePondering()
            elif (strMessage.StartsWith("variant ")) then
                // do nothing 
                raise(WinBoardInputException("Unknown command: " + strMessage))
            elif (strMessage="quit") then
                // Kill sharpchess
                let HandleWinBoardQuitBase() =
                    Application.Exit()
                let HandleWinBoardQuit() =
                    if Funcs.frmWinBoard.InvokeRequired then
                        Funcs.frmWinBoard.Invoke(new MethodInvoker(fun() -> HandleWinBoardQuitBase())) |> ignore
                    else
                        HandleWinBoardQuitBase()
                HandleWinBoardQuit()
            elif (strMessage="random") then
                // GNU Chess 4 specific - so ignore
                ()
            elif (strMessage="force") then
                // Set the engine to play neither color ("force mode"). Stop clocks. The engine should check that moves 
                // received in force mode are legal and made in the proper turn, but should not think, ponder, or make 
                // moves of its own. 
                Funcs.SuspendPondering()
                Funcs.gm.PlayerToPlay.Clock.Stop()
                Attack.PlayerWhite.Intellegence <- Human
                Attack.PlayerBlack.Intellegence <- Human
            elif (strMessage="go") then
                // Leave force mode and set the engine to play the color that is on move. 
                // Associate the engine's clock with the color that is on move, the opponent's clock with the color that 
                // is not on move. Start the engine's clock. Start thinking and eventually make a move. 
                Funcs.SuspendPondering()
                Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Clock.Stop()
                Funcs.gm.PlayerToPlay.Intellegence <- Computer
                Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Intellegence <- Human
                Funcs.gm.PlayerToPlay.Clock.Start()
                Funcs.StartThinking(Funcs.gm.PlayerToPlay)
            elif (strMessage="playother") then
                (*
                (This command is new in protocol version 2. It is not sent unless you enable it with the feature command.) 
                Leave force mode and set the engine to play the color that is not on move. Associate the opponent's 
                clock with the color that is on move, the engine's clock with the color that is not on move. Start the 
                opponent's clock. If pondering is enabled, the engine should begin pondering. If the engine later 
                receives a move, it should start thinking and eventually reply. 
                *)
                Funcs.SuspendPondering()
                Funcs.gm.PlayerToPlay <- Attack.OtherPlayer(Funcs.gm.PlayerToPlay)
                Funcs.gm.PlayerToPlay.Intellegence <- Computer
                Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Intellegence <- Human
                Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Clock.Stop()
                Funcs.gm.PlayerToPlay.Clock.Start()
                Funcs.ResumePondering()
            elif (strMessage="white") then
                // (This command is obsolete as of protocol version 2, but is still sent in situations to accommodate 
                // older engines unless you disable it with the feature command.) 
                // Set White on move. Set the engine to play Black. Stop clocks. 
                Funcs.SuspendPondering()
                if (Funcs2.IsThinking(Funcs.gm.PlayerToPlay)) then
                    Funcs.ForceImmediateMove(Funcs.gm.PlayerToPlay)
                Funcs.gm.PlayerToPlay.Clock.Stop()
                Funcs.gm.PlayerToPlay <- Attack.PlayerWhite
                Attack.PlayerWhite.Intellegence <- Human
                Attack.PlayerBlack.Intellegence <- Computer
            elif (strMessage="black") then
                // (This command is obsolete as of protocol version 2, but is still sent in situations to accommodate 
                // older engines unless you disable it with the feature command.) 
                // Set Black on move. Set the engine to play White. Stop clocks. 
                Funcs.SuspendPondering()
                if (Funcs2.IsThinking(Funcs.gm.PlayerToPlay)) then
                    Funcs.ForceImmediateMove(Funcs.gm.PlayerToPlay)
                Funcs.gm.PlayerToPlay.Clock.Stop()
                Funcs.gm.PlayerToPlay <- Attack.PlayerBlack
                Attack.PlayerWhite.Intellegence <- Computer
                Attack.PlayerBlack.Intellegence <- Human
            elif (strMessage.StartsWith("level ")) then
                wbSetLevel(strMessage.Substring("level ".Length))
            elif (strMessage.StartsWith("st ")) then
                // Set time Absolute fixed time-er-move. No time is carried forward from one move to the next. 
                // The commands "level" and "st" are not used together. 
                Funcs.ClockMoves <- 1
                Funcs.gmtim.ClockTime <- new TimeSpan(0, 0, 0)
                Funcs.gmtim.ClockIncrementPerMove <- new TimeSpan(0, 0, 0)
                Funcs.gmtim.ClockFixedTimePerMove <- new TimeSpan(0, 0, System.Int32.Parse(strMessage.Substring("st ".Length)) )
                Funcs.DifficultyLevel <- 0
                Search.MaximumSearchDepth <- 32
                Funcs.EnablePondering <- true
            elif (strMessage.StartsWith("depth ")) then
                // The engine should limit its thinking to DEPTH ply. 
                Search.MaximumSearchDepth <- System.Int32.Parse( strMessage.Substring("level ".Length) )
            elif (strMessage.StartsWith("time ")) then
                Funcs.SuspendPondering()
                (*
                Set a clock that always belongs to the engine. N is a number in centiseconds (units of 1/100 second). 
                Even if the engine changes to playing the opposite color, this clock remains with the engine.
                *)
                if (Funcs.gmtim.ClockFixedTimePerMove.Ticks > 0L) then
                    Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Clock.TimeElapsed <- ( new TimeSpan(Funcs.gmtim.ClockFixedTimePerMove.Ticks*int64(Funcs.MoveNo(Funcs.gm)))) - (new TimeSpan(System.Int64.Parse(strMessage.Substring("time ".Length))*100000L))
                elif (Funcs.gmtim.ClockIncrementPerMove.Ticks > 0L) then
                    Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Clock.TimeElapsed <- ( new TimeSpan(Funcs.gmtim.ClockTime.Ticks + Funcs.gmtim.ClockIncrementPerMove.Ticks*int64(Funcs.MoveNo(Funcs.gm)))) - (new TimeSpan(System.Int64.Parse(strMessage.Substring("time ".Length))*100000L))
                else
                    Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Clock.TimeElapsed <- (new TimeSpan(Funcs.gmtim.ClockTime.Ticks * int64(Attack.OtherPlayer(Funcs.gm.PlayerToPlay).Clock.ControlPeriod(Funcs.ClockMoves,Funcs.MoveNo(Funcs.gm))))) - (new TimeSpan(System.Int64.Parse(strMessage.Substring("time ".Length)) * 100000L))
            elif (strMessage.StartsWith("otim ")) then
                Funcs.SuspendPondering()
                (*
                Set a clock that always belongs to the opponent. N is a number in centiseconds (units of 1/100 second). Even if the opponent changes to playing the opposite color, this clock remains with the opponent. 
                If needed for purposes of board display in force mode (where the engine is not participating in the 
                game) the time clock should be associated with the last color that the engine was set to play, the 
                otim clock with the opposite color. 
                Beginning in protocol version 2, if you can't handle the time and otim commands, you can use the 
                "feature" command to disable them; see below. The following techniques from older protocol versions 
                also work: You can ignore the time and otim commands (that is, treat them as no-ops), 
                or send back "Error (unknown command): time" the first time you see "time". 
                *)
                if (Funcs.gmtim.ClockFixedTimePerMove.Ticks > 0L) then 
                    Funcs.gm.PlayerToPlay.Clock.TimeElapsed <- ( new TimeSpan(Funcs.gmtim.ClockFixedTimePerMove.Ticks*int64(Funcs.MoveNo(Funcs.gm)))) - (new TimeSpan(System.Int64.Parse(strMessage.Substring("time ".Length))*100000L))
                elif (Funcs.gmtim.ClockIncrementPerMove.Ticks > 0L) then
                    Funcs.gm.PlayerToPlay.Clock.TimeElapsed <- ( new TimeSpan(Funcs.gmtim.ClockTime.Ticks + Funcs.gmtim.ClockIncrementPerMove.Ticks*int64(Funcs.MoveNo(Funcs.gm)))) - (new TimeSpan(System.Int64.Parse(strMessage.Substring("time ".Length))*100000L))
                else
                    Funcs.gm.PlayerToPlay.Clock.TimeElapsed <- (new TimeSpan(Funcs.gmtim.ClockTime.Ticks * int64(Funcs.gm.PlayerToPlay.Clock.ControlPeriod(Funcs.ClockMoves,Funcs.MoveNo(Funcs.gm))))) - (new TimeSpan(System.Int64.Parse(strMessage.Substring("time ".Length)) * 100000L))
            elif (strMessage.StartsWith("usermove ")) then
                (*
                By default, moves are sent to the engine without a command name; the notation is just sent as a line 
                by itself. Beginning in protocol version 2, you can use the feature command to cause the command name
                "usermove" to be sent before the move. Example: "usermove e2e4". 
                *)
                wbMakeMove(strMessage.Substring("usermove ".Length))
            elif (strMessage="?") then
                (* 
                Move now. If your engine is thinking, it should move immediately; otherwise, the command should be 
                ignored (treated as a no-op). It is permissible for your engine to always ignore the ? command. 
                The only bad consequence is that xboard's Move Now menu command will do nothing. 
                *)
                if (Funcs2.IsThinking(Funcs.gm.PlayerToPlay) && not Funcs2.IsPondering) then
                    Funcs.ForceImmediateMove(Funcs.gm.PlayerToPlay)
            elif (strMessage.StartsWith("ping ")) then
                (*
                In this command, N is a decimal number. When you receive the command, reply by sending the string 
                pong N, where N is the same number you received. Important: You must not reply to a "ping" command 
                until you have finished executing all commands that you received before it. Pondering does not count; 
                if you receive a ping while pondering, you should reply immediately and continue pondering. Because 
                of the way xboard uses the ping command, if you implement the other commands in this protocol, you 
                should never see a "ping" command when it is your move; however, if you do, you must not send the 
                "pong" reply to xboard until after you send your move. For example, xboard may send "?" immediately 
                followed by "ping". If you implement the "?" command, you will have moved by the time you see the 
                subsequent ping command. Similarly, xboard may send a sequence like "force", "new", "ping". You must 
                not send the pong response until after you have finished executing the "new" command and are ready 
                for the new game to start. The ping command is new in protocol version 2 and will not be sent unless 
                you enable it with the "feature" command. Its purpose is to allow several race conditions that could 
                occur in previous versions of the protocol to be fixed, so it is highly recommended that you implement 
                it. It is especially important in simple engines that do not ponder and do not poll for input while 
                thinking, but it is needed in all engines. 
                *)
                while (Funcs2.IsThinking(Funcs.gm.PlayerToPlay) && not Funcs2.IsPondering) do
                    // Wait for thinking to finish
                    Thread.Sleep(250)
                Funcs.wbSendOutputMessage("pong " + strMessage.Substring(5))
            elif (strMessage="draw") then
                (*
                The engine's opponent offers the engine a draw. To accept the draw, send "offer draw". 
                To decline, ignore the offer (that is, send nothing). If you're playing on ICS, it's possible for the 
                draw offer to have been withdrawn by the time you accept it, so don't assume the game is over because 
                you accept a draw offer. Continue playing until xboard tells you the game is over. See also 
                "offer draw" below. 
                *)
                // Ignore all draw offer for now.
                ()
            elif (strMessage.StartsWith("result ")) then
                (*
                After the end of each game, xboard will send you a result command. You can use this command to trigger learning. RESULT is either 1-0, 0-1, 1/2-1/2, or *, indicating whether white won, black won, the game was a draw, or the game was unfinished. The COMMENT string is purely a human-readable comment; its content is unspecified and subject to change. In ICS mode, it is passed through from ICS uninterpreted. Example: 
                result 1-0 {White mates}
                Here are notes on interpreting the "result" command. apply only to playing on ICS ("Zippy" mode). 

                If you won but did not just play a mate, your opponent must have resigned or forfeited. If you lost but were not just mated, you probably forfeited on time, or perhaps the operator resigned manually. If there was a draw for nonobvious reason, perhaps your opponent called your flag when he had insufficient mating material (or vice versa), or perhaps the operator agreed to a draw manually. 

                You will get a result command even if you already know the game ended -- for example, after you just checkmated your opponent. In fact, if you send the "RESULT {COMMENT}" command (discussed below), you will simply get the same thing fed back to you with "result" tacked in front. You might not always get a "result *" command, however. In particular, you won't get one in local chess engine mode when the user stops playing by selecting Reset, Edit Game, Exit or the like. 
                *)
                Funcs.SuspendPondering()
                Funcs.gm.PlayerToPlay.Clock.Stop()
            elif (strMessage.StartsWith("setboard ")) then
                (*
                The setboard command is the new way to set up positions, beginning in protocol version 2. 
                It is not used unless it has been selected with the feature command. 
                Here FEN is a position in Forsythe-Edwards Notation, as defined in the PGN standard. 
                Illegal positions: Note that either setboard or edit can be used to send an illegal position to the engine. 
                The user can create any position with xboard's Edit Position command (even, say, an empty board, or a 
                board with 64 white kings and no black ones). If your engine receives a position that it considers 
                illegal, I suggest that you send the response "tellusererror Illegal position", and then respond to 
                any attempted move with "Illegal move" until the next new, edit, or setboard command.
                *)
                try
                    Funcs.New( strMessage.Substring(9).Trim() )
                with 
                    |(Funcs.FENValidationException x) -> Funcs.wbSendOutputMessage("tellusererror Illegal position: " + x.ToString());
            elif (strMessage="edit") then
                (*
                The edit command is the old way to set up positions. For compatibility with old engines, it is still used by default, but new engines may prefer to use the feature command (see below) to cause xboard to use setboard instead. The edit command puts the chess engine into a special mode, where it accepts the following subcommands: c change current piece color, initially white  
                Pa4 (for example) place pawn of current color on a4  
                xa4 (for example) empty the square a4 (not used by xboard)  
                # clear board  
                . leave edit mode  
                See the Idioms section below for additional subcommands used in ChessBase's implementation of the protocol. 
                The edit command does not change the side to move. To set up a black-on-move position, xboard uses the following command sequence: 
                new
                force
                a2a3
                edit
                <edit commands>
                .
                This sequence is used to avoid the "black" command, which is now considered obsolete and which many engines never did implement as specified in this document. 
                After an edit command is complete, if a king and a rook are on their home squares, castling is assumed to be available to them. En passant capture is assumed to be illegal on the current move regardless of the positions of the pawns. The clock for the 50 move rule starts at zero, and for purposes of the draw by repetition rule, no prior positions are deemed to have occurred. 
                *) 
                raise(WinBoardInputException("Unknown command: " + strMessage))
            elif (strMessage="hint") then
                // If the user asks for a hint, xboard sends your engine the command "hint". Your engine should respond with "Hint: xxx", where xxx is a suggested move. If there is no move to suggest, you can ignore the hint command (that is, treat it as a no-op). 
                raise(WinBoardInputException("Unknown command: " + strMessage))
            elif (strMessage="bk") then
                // If the user selects "Book" from the xboard menu, xboard will send your engine the command "bk". You can send any text you like as the response, as long as each line begins with a blank space or tab (\t) character, and you send an empty line at the end. The text pops up in a modal information dialog. 
                raise(WinBoardInputException("Unknown command: " + strMessage))
            elif (strMessage="undo") then
                // If the user asks to back up one move, xboard will send you the "undo" command. xboard will not send this command without putting you in "force" mode first, so you don't have to worry about what should happen if the user asks to undo a move your engine made. (GNU Chess 4 actually switches to playing the opposite color in this case.) 
                Funcs.UndoMove()
            elif (strMessage="remove") then
                // If the user asks to retract a move, xboard will send you the "remove" command. It sends this command only when the user is on move. Your engine should undo the last two moves (one for each player) and continue playing the same color.
                Funcs.UndoMove()
                Funcs.UndoMove()
            elif (strMessage="hard") then
                (*
                Turn on pondering (thinking on the opponent's time, also known as "permanent brain"). xboard will not 
                make any assumption about what your default is for pondering or whether "new" affects this setting. 
                *)
                Funcs.EnablePondering <- true
            elif (strMessage="easy") then
                // Turn off pondering. 
                Funcs.EnablePondering <- false
            elif (strMessage="post") then
                // Turn on thinking/pondering output. See Thinking Output section. 
                Funcs.ShowThinking <- true
            elif (strMessage="nopost") then
                // Turn off thinking/pondering output. 
                Funcs.ShowThinking <- false
            elif (strMessage="analyze") then
                // Enter analyze mode. 
                Funcs.SuspendPondering()
                Funcs.gm.PlayerToPlay.Clock.Stop()
                Attack.PlayerWhite.Intellegence <- Computer
                Attack.PlayerBlack.Intellegence <- Computer
                Funcs2.IsInAnalyseMode <- true
                Funcs.StartThinking(Funcs.gm.PlayerToPlay)
            elif (strMessage=".") then
                // Status update - jsut make sure Funcs.ShowThinking is on
                Funcs.ShowThinking <- true
            elif (strMessage="exit") then
                // Exit analyze mode.
                Funcs.SuspendPondering()
                if (Funcs2.IsInAnalyseMode) then
                    Funcs2.IsInAnalyseMode <- false
            elif (strMessage.StartsWith("name ")) then
                // This command informs the engine of its opponent's name. When the engine is playing on a chess server, xboard obtains the opponent's name from the server. When the engine is playing locally against a human user, xboard obtains the user's login name from the local operating system. When the engine is playing locally against another engine, xboard uses either the other engine's filename or the name that the other engine supplied in the myname option to the feature command. By default, xboard uses the name command only when the engine is playing on a chess server. Beginning in protocol version 2, you can change this with the name option to the feature command; see below. 
                ()
            elif (strMessage="rating") then
                (*
                In ICS mode, xboard obtains the ICS opponent's rating from the "Creating:" message that appears before each game. (This message may not appear on servers using outdated versions of the FICS code.) In Zippy mode, it sends these ratings on to the chess engine using the "rating" command. The chess engine's own rating comes first, and if either opponent is not rated, his rating is given as 0. In the future this command may also be used in other modes, if ratings are known. Example: 
                rating 2600 1500
                *)
                raise(WinBoardInputException("Unknown command: " + strMessage))
            elif (strMessage.StartsWith("ics ")) then
                // If HOSTNAME is "-", the engine is playing against a local opponent; otherwise, the engine is playing on an Internet Chess Server (ICS) with the given hostname. This command is new in protocol version 2 and is not sent unless the engine has enabled it with the "feature" command. Example: "ics freechess.org" 
                raise(WinBoardInputException("Unknown command: " + strMessage))
            elif (strMessage="computer") then
                // The opponent is also a computer chess engine. engines alter their playing style when they receive this command.
                ()
            elif (strMessage="pause") then
                // See resume
                Funcs.PausePlay()
            elif (strMessage="resume") then
                // (These commands are new in protocol version 2 and will not be sent unless feature pause=1 is set. At this writing, xboard actually does not use the commands at all, but it or other interfaces may use them in the future.) The "pause" command puts the engine into a special state where it does not think, ponder, or otherwise consume significant CPU time. The current thinking or pondering (if any) is suspended and both player's clocks are stopped. The only command that the interface may send to the engine while it is in the paused state is "resume". The paused thinking or pondering (if any) resumes from exactly where it left off, and the clock of the player on move resumes running from where it stopped. 
                Funcs.ResumePlay()
            elif (strMessage.Length=4 || strMessage.Length=5) then
                wbMakeMove(strMessage)
            else
                raise(WinBoardInputException("Unknown command: " + strMessage))
        with
            |(WinBoardInputException e) -> Funcs.wbSendOutputMessage(e)
    let LogWinBoardInput(strMessage) =
        Funcs.LogWinBoardMessage("In", strMessage)
        ProcessInputEvent(strMessage)
    let wbDetermineStatus() =
        let strMessage = Console.ReadLine()
        Funcs.wbActive <- (strMessage="xboard")
        if (Funcs.wbActive) then
            LogWinBoardInput(strMessage)
            Funcs.wbSendOutputMessage("\n")
    let wbListen() =
        let dtmStarted = DateTime.Now
        let mutable strMessage = ""
        while (true) do
            strMessage <- Console.ReadLine()
            if (strMessage=null) then
                Thread.Sleep(500)
            else
                LogWinBoardInput(strMessage)
    let wbStartListener() =
        if Funcs.wbActive then
            threadWinBoardListener <- new Thread(new ThreadStart(wbListen))
            threadWinBoardListener.IsBackground <- true
            threadWinBoardListener.Priority <- System.Threading.ThreadPriority.Normal
            threadWinBoardListener.Start()

